﻿//evaluation
namespace FSharpChess
open System

module Eval =
    let ValuePawn = 100
    let ValueBishop = 325
    let ValueKnight = 325
    let ValueRook = 500
    let ValueQueen = 1000
    let ValueKing = 10000
    let Value(pcid,psn) = 
        let pc = psn.Pcs.[pcid]
        match pc.Name with
            |enmName.Pawn -> if pc.SquareOrd<> -1 && (Ref.ri.[pc.SquareOrd] = 7 || Ref.ri.[pc.SquareOrd] = 0) then 85 else ValuePawn
            |enmName.Bishop -> ValueBishop
            |enmName.Knight -> ValueKnight
            |enmName.Rook -> ValueRook
            |enmName.Queen -> ValueQueen
            |enmName.King -> ValueKing
            |_ -> raise InvalidName
    let PositionalPoints(pcid,psn) =
        let pc = psn.Pcs.[pcid]
        let pccol = pc.pcIsWhite
        let pcord = pc.SquareOrd
        //functions
        let CanBeDrivenAwayByPawn(ord) =
            let rec canbed ordl =
                match ordl with
                    |[] -> false
                    |o ->
                        let pieceid = psn.Sqs.[o.Head]
                        if (pieceid<> -1 && psn.Pcs.[pieceid].pcIsWhite <> pccol && psn.Pcs.[pieceid].Name = enmName.Pawn) then true else canbed o.Tail
            canbed (if pccol then Ref.canattPWto.[ord] else Ref.canattPBto.[ord])
        let DefensePoints() =
            let pc = psn.Pcs.[pcid]
            let DefensedByQ = ref false
            let rec DefByRays nm r rl =
                match r with
                    |[] -> if rl=[] then false else DefByRays nm rl.Head rl.Tail
                    | r ->  
                        let cord = r.Head 
                        if psn.Sqs.[cord] = -1 then
                            DefByRays nm r.Tail rl 
                        elif psn.Pcs.[psn.Sqs.[cord]].pcIsWhite = pc.pcIsWhite && psn.Pcs.[psn.Sqs.[cord]].Name=nm then
                            true
                        elif psn.Pcs.[psn.Sqs.[cord]].pcIsWhite = pc.pcIsWhite && psn.Pcs.[psn.Sqs.[cord]].Name=enmName.Queen then
                            DefensedByQ := true
                            if rl=[] then false else DefByRays nm rl.Head rl.Tail
                        else
                            if rl=[] then false else DefByRays nm rl.Head rl.Tail
            let rec DefByMovs nm ml =
                match ml with
                    |[] -> false
                    |ml ->  
                        let cord = ml.Head 
                        if psn.Sqs.[cord] = -1 then
                            DefByMovs nm ml.Tail 
                        elif psn.Pcs.[psn.Sqs.[cord]].pcIsWhite = pc.pcIsWhite && psn.Pcs.[psn.Sqs.[cord]].Name=nm then
                            true
                        else
                            DefByMovs nm ml.Tail
            let DefensedByP() = DefByMovs enmName.Pawn (if pc.pcIsWhite then Ref.attsPWto.[pc.SquareOrd] else Ref.attsPBto.[pc.SquareOrd])
            let DefensedByB() = DefByRays enmName.Bishop Ref.raysB.[pc.SquareOrd].Head Ref.raysB.[pc.SquareOrd].Tail
            let DefensedByR() = DefByRays enmName.Rook Ref.raysR.[pc.SquareOrd].Head Ref.raysR.[pc.SquareOrd].Tail
            let DefensedByN() = DefByMovs enmName.Knight Ref.movsN.[pc.SquareOrd]
            let DefensedByK() = DefByMovs enmName.King Ref.movsK.[pc.SquareOrd]
            if DefensedByP() then
                6
            elif DefensedByN() then
                5
            elif DefensedByB() then
                5
            elif DefensedByR() then
                3
            elif !DefensedByQ then
                2
            elif DefensedByK() then
                2
            else
                0
        let TaxiCabDistanceToEnemyKingPenalty() =
            let sqid = psn.Pcs.[pcid].SquareOrd
            if sqid <> -1 then
                if pcid<16 then
                    Math.Abs(Ref.ri.[sqid] - Ref.ri.[psn.Pcs.[20].SquareOrd]) + Math.Abs(Ref.fi.[sqid] - Ref.fi.[psn.Pcs.[20].SquareOrd])
                else
                    Math.Abs(Ref.ri.[sqid] - Ref.ri.[psn.Pcs.[4].SquareOrd]) + Math.Abs(Ref.fi.[sqid] - Ref.fi.[psn.Pcs.[4].SquareOrd])
            else
                0
        //mobility
        let rec getSquaresRays (sqrs:int[]) i r rl =
            match r with
                |[] -> if rl<>[] then getSquaresRays sqrs i rl.Head rl.Tail
                | r ->  
                    let cord = r.Head 
                    if psn.Sqs.[cord] = -1 then
                        sqrs.[i] <- cord
                        getSquaresRays sqrs (i+1) r.Tail rl 
                    elif (psn.Pcs.[psn.Sqs.[cord]].pcIsWhite <> pccol && psn.Pcs.[psn.Sqs.[cord]].Name<>enmName.King) then
                        sqrs.[i] <- cord
                        if rl<>[] then getSquaresRays sqrs (i+1) rl.Head rl.Tail
                    else
                        if rl<>[] then getSquaresRays sqrs i rl.Head rl.Tail
        //main split
        let mutable intPoints = 0
        match pc.Name with
            |enmName.Pawn ->
                let caps = if pccol then Ref.attsPW.[pcord] else Ref.attsPB.[pcord]
                let EvalForkPawn =
                    if caps.Length<2 then
                        0
                    else
                        let pieceid1 = psn.Sqs.[caps.[0]]
                        let pieceid2 = psn.Sqs.[caps.[1]]
                        if pieceid1 = -1 && pieceid2 = -1 then
                            0
                        elif pieceid1 = -1 then
                            if psn.Pcs.[pieceid2].pcIsWhite <> pccol && psn.Pcs.[pieceid2].Name<>enmName.King && psn.Pcs.[pieceid2].Name <> enmName.Pawn then 30 else 0
                        elif pieceid2 = -1 then
                            if psn.Pcs.[pieceid1].pcIsWhite <> pccol && psn.Pcs.[pieceid1].Name<>enmName.King && psn.Pcs.[pieceid1].Name <> enmName.Pawn then 30 else 0
                        else
                            if psn.Pcs.[pieceid2].pcIsWhite <> pccol && psn.Pcs.[pieceid2].Name<>enmName.King && psn.Pcs.[pieceid2].Name <> enmName.Pawn && psn.Pcs.[pieceid1].pcIsWhite <> pccol && psn.Pcs.[pieceid1].Name<>enmName.King && psn.Pcs.[pieceid1].Name <> enmName.Pawn then
                                Math.Min(Value(pieceid1,psn), Value(pieceid2,psn))
                            elif psn.Pcs.[pieceid2].pcIsWhite <> pccol && psn.Pcs.[pieceid2].Name<>enmName.King && psn.Pcs.[pieceid2].Name <> enmName.Pawn then
                                3
                            elif psn.Pcs.[pieceid1].pcIsWhite <> pccol && psn.Pcs.[pieceid1].Name<>enmName.King && psn.Pcs.[pieceid1].Name <> enmName.Pawn then
                                3
                            else
                                0
                // PENALITIES
                // Isolated or Doubled or Backward or Passed pawn
                let pcfil = Ref.fi.[pcord]
                let pcrnk = Ref.ri.[pcord]
                let getisbl =
                    let bot = if pccol then 8 else 24
                    let top = if pccol then 15 else 31
                    let mutable il = true
                    let mutable ir = true
                    let mutable db = false
                    let mutable bw = true
                    for i = bot to top do
                        let piece = psn.Pcs.[i]
                        let ord = piece.SquareOrd
                        if ord <> -1 then
                            let fil = Ref.fi.[ord]
                            if piece.Name = enmName.Pawn && piece <> pc then
                                if fil = pcfil then db <- true  
                                if fil = pcfil-1 then il <- false  
                                if fil = pcfil+1 then ir <- false
                                if ord=pcord+1||ord=pcord-1||(fil=pcfil-1||fil=pcfil+1)&&(pccol && ord<pcord||not pccol && ord>pcord) then bw <- false
                    il,ir,db,bw  
                let getpd =
                    let bot = if not pccol then 8 else 24
                    let top = if not pccol then 15 else 31
                    let rnktest rnk = if pccol then rnk>pcrnk else rnk<pcrnk 
                    let mutable pd = true
                    for i = bot to top do
                        let piece = psn.Pcs.[i]
                        let ord = piece.SquareOrd
                        if ord <> -1 then
                            let fil = Ref.fi.[ord]
                            if piece.Name=enmName.Pawn && (rnktest Ref.ri.[ord]) && (fil=pcfil||fil=pcfil+1||fil=pcfil-1) then pd <- false
                    pd  
                let blnIsIsolatedLeft,blnIsIsolatedRight, blnIsDouble,blnIsBackward = getisbl
                let blnIsPassed = getpd
                let ideds=[|1;3;4;5;5;4;3;1|]
                if blnIsIsolatedLeft then intPoints <- intPoints - ideds.[pcfil]
                if blnIsIsolatedRight then intPoints <- intPoints - ideds.[pcfil]
                if blnIsDouble then intPoints <- intPoints - 7
                if blnIsIsolatedLeft && blnIsIsolatedRight then intPoints <- intPoints - 10
                if blnIsBackward then intPoints <- intPoints - 3
                if Funcs2.Stage(psn)<>End && (pcfil=3||pcfil=4)&&(pccol && pcrnk=1||not pccol && pcrnk=6) then intPoints <- intPoints - 2//Pawns on D or E files - still at rank 2 
                if Funcs2.Stage(psn)=Opening && (pcfil=3||pcfil=4)&&(pccol && pcrnk=4||not pccol && pcrnk=3) then intPoints <- intPoints + 3//	Pawns on D or E files - Bonus for rank 5
                // Encourage capturing towards the centre
                let aintFileBonus = [|0;6;16;32;32;16;6;0|]
                // Advancement
                let aintAdvancementBonus = [|0;0;0;45;75;120;240;999|]
                let mutable intAdvancementBonus = if pccol then aintAdvancementBonus.[pcrnk] else aintAdvancementBonus.[7-pcrnk]
                if blnIsPassed then 
                    intPoints <- intPoints + 80
                    intAdvancementBonus <- (intAdvancementBonus <<< 1)/10
                // Slowly increase advancement bonus as material drops away.
                intAdvancementBonus <- intAdvancementBonus * ((32 - psn.Wct - psn.Bct) * 2) / 32
                intPoints + intAdvancementBonus + EvalForkPawn + aintFileBonus.[pcfil]
            |enmName.Bishop ->
                let bishopAintSquareValues =
                    [|
                    10;10;10;10;10;10;10;10;
                    10;25;20;20;20;20;25;10;
                    10;49;30;30;30;30;49;10;
                    10;20;30;40;40;30;20;10;
                    10;20;30;40;40;30;20;10;
                    10;49;30;30;30;30;49;10;
                    10;25;20;20;20;20;25;10;
                    10;10;10;10;10;10;10;10
                    |]
                intPoints <- intPoints + (bishopAintSquareValues.[pcord] <<< 1)/10
                if Funcs2.Stage(psn) <> End && CanBeDrivenAwayByPawn(pcord) then intPoints <- intPoints - 30
                // Mobility
                let sqrs = Array.create 15 -1
                getSquaresRays sqrs 0 Ref.raysB.[pcord].Head Ref.raysB.[pcord].Tail
                let rec getsqval vl i =
                    let sq = sqrs.[i]
                    if sq= -1 then vl else getsqval (vl+bishopAintSquareValues.[sq]) (i+1)
                let intSquareValue = getsqval bishopAintSquareValues.[pcord] 0
                intPoints + DefensePoints() + (intSquareValue >>> 2)/10
            |enmName.Knight ->
                let knightAintSquareValues =
                    [|
                    1; 1; 1; 1; 1; 1; 1; 1;
                    1; 7; 7; 7; 7; 7; 7; 1;
                    1; 7;18;18;18;18; 7; 1;
                    1; 7;18;27;27;18; 7; 1;
                    1; 7;18;27;27;18; 7; 1;
                    1; 7;18;18;18;18; 7; 1;
                    1; 7; 7; 7; 7; 7; 7; 1;
                    1; 1; 1; 1; 1; 1; 1; 1
                    |]
                if (Funcs2.Stage(psn) = End) then
                    intPoints <- intPoints - (TaxiCabDistanceToEnemyKingPenalty() <<< 4)
                else
                    intPoints <- intPoints + (knightAintSquareValues.[pcord] <<< 3)/10
                if CanBeDrivenAwayByPawn(pcord) then intPoints <- intPoints - 3
                intPoints + DefensePoints()
            |enmName.Rook ->
                // After the opening, Rooks are penalized slightly depending on "taxicab" distance to the enemy king.
                if (Funcs2.Stage(psn) <> Opening) then intPoints <- intPoints - TaxiCabDistanceToEnemyKingPenalty()
                if (Funcs2.Stage(psn) <> End) then
                    // Rooks are given a bonus of 10(0) points for occupying a file with no friendly pawns and a bonus of 
                    // 4(0) points if no enemy pawns lie on that file. 
                    let rec getfrnd ordl hf he =
                        if ordl<>[] then
                            let pieceid = psn.Sqs.[ordl.Head]
                            if (pieceid <> -1 && psn.Pcs.[pieceid].Name = enmName.Pawn) then
                                if (psn.Pcs.[pieceid].pcIsWhite = pccol) then
                                    if he then true,true else getfrnd ordl.Tail true false
                                else
                                    if hf then true,true else getfrnd ordl.Tail false true
                            else
                                getfrnd ordl.Tail hf he
                        else hf,he
                    let blnHasFiendlyPawn, blnHasEnemyPawn = getfrnd (Ref.fileRaysR.[pcord]) false false 
                    if (not blnHasFiendlyPawn) then intPoints <- intPoints + 2
                    if (not blnHasEnemyPawn) then intPoints <- intPoints + 1
                    // 7th rank
                    if (pccol && Ref.ri.[pcord] = 6|| not pccol && Ref.ri.[pcord] = 1) then intPoints <- intPoints + 3
                // Mobility
                let rookAintSquareValues =
                    [|
                    10;10;10;10;10;10;10;10;
                    10;20;20;20;20;20;20;10;
                    10;20;30;30;30;30;20;10;
                    10;20;30;40;40;30;20;10;
                    10;20;30;40;40;30;20;10;
                    10;20;30;30;30;30;20;10;
                    10;20;20;20;20;20;20;10;
                    10;10;10;10;10;10;10;10
                    |]
                let sqrs = Array.create 15 -1
                getSquaresRays sqrs 0 Ref.raysR.[pcord].Head Ref.raysR.[pcord].Tail
                let rec getsqval vl i =
                    let sq = sqrs.[i]
                    if sq= -1 then vl else getsqval (vl+rookAintSquareValues.[sq]) (i+1)
                let intSquareValue = getsqval rookAintSquareValues.[pcord] 0
                intPoints + DefensePoints() + (intSquareValue >>> 2)/10
            |enmName.Queen ->
                // The queen is that after the opening it is penalized slightly for 
                // "taxicab" distance to the enemy king.
                if (Funcs2.Stage(psn) = Opening) then
                    if (pccol) then
                        intPoints <- intPoints - Ref.ri.[pcord] * 7
                    else
                        intPoints <- intPoints - (7 - Ref.ri.[pcord]) * 7
                else
                    intPoints <- intPoints - TaxiCabDistanceToEnemyKingPenalty()
                intPoints + DefensePoints()
            |enmName.King ->
                let Openness() =
                    //ray based moves
                    let rec getOpenRays r rl ip =
                        match r with
                            |[] -> if rl<>[] && ip<=90 then getOpenRays rl.Head rl.Tail ip else ip
                            | r ->  
                                let cord = r.Head 
                                let sq = psn.Sqs.[cord]
                                if (sq = -1 || (psn.Pcs.[sq].Name <> enmName.Pawn && psn.Pcs.[sq].Name <> enmName.Rook) || psn.Pcs.[sq].pcIsWhite <> pccol) then
                                    getOpenRays r.Tail rl (ip+7)
                                else
                                    if rl<>[] && ip<=90 then getOpenRays rl.Head rl.Tail ip else ip
                    if pccol then getOpenRays Ref.OpenWK.[pcord].Head Ref.OpenWK.[pcord].Tail 0 else getOpenRays Ref.OpenBK.[pcord].Head Ref.OpenBK.[pcord].Tail 0
                let OtherHasQueen = if not pccol then psn.Pcs.[3].SquareOrd <> -1 else psn.Pcs.[19].SquareOrd <> -1
                if (Funcs2.Stage(psn) <> Opening && OtherHasQueen) then
                    // Penalty for not having pawn directly in front
                    let ordid = if pccol then Ref.pawnOneWK.[pcord] else Ref.pawnOneBK.[pcord]
                    let pieceid = if ordid = -1 then -1 else psn.Sqs.[ordid]
                    if (pieceid = -1 || psn.Pcs.[pieceid].Name <> enmName.Pawn || psn.Pcs.[pieceid].pcIsWhite <> pccol) then
                        intPoints <- intPoints - 7
                        let ordid = if ordid = -1 then -1 elif pccol then Ref.pawnOneWK.[ordid] else Ref.pawnOneBK.[ordid]
                        let pieceid = if ordid = -1 then -1 else psn.Sqs.[ordid]
                        if (pieceid = -1 || psn.Pcs.[pieceid].Name <> enmName.Pawn || psn.Pcs.[pieceid].pcIsWhite <> pccol) then intPoints <- intPoints - 15
                    // Penalty for first movement the king, other than castling. pc is to stop the king dancing around its
                    // own pawns in an attempt to get better protection, at the expense of developing other pieces.
                    if (not pccol && psn.Bcas)||(pccol && psn.Wcas) then
                        if (pc.NoOfMoves >= 2) then intPoints <- intPoints - 20
                    else
                        if (pc.NoOfMoves >= 1) then intPoints <- intPoints - 20
                    // Penalty for number of open lines to king
                    intPoints <- intPoints - Openness()
                    // Penalty for half-open adjacent files
                    let rec hasFP ordl =
                        if ordl<>[] then
                            let pieceid = psn.Sqs.[ordl.Head]
                            if (pieceid <> -1 && psn.Pcs.[pieceid].Name = enmName.Pawn && psn.Pcs.[pieceid].pcIsWhite = pccol) then
                                true
                            else hasFP ordl.Tail
                        else false
                    let blnHasFiendlyPawn = hasFP (if pccol then Ref.frRaysWP.[pcord] else Ref.frRaysBP.[pcord])
                    if not blnHasFiendlyPawn then intPoints <- intPoints - 20
                    let blnHasFiendlyPawn = hasFP (if pccol then Ref.frRaysWPl.[pcord] else Ref.frRaysBPl.[pcord])
                    if not blnHasFiendlyPawn then intPoints <- intPoints - 20
                    let blnHasFiendlyPawn = hasFP (if pccol then Ref.frRaysWPr.[pcord] else Ref.frRaysBPr.[pcord])
                    if not blnHasFiendlyPawn then intPoints <- intPoints - 20
                let kingAintSquareValues =
                    [|
                    1; 1; 1; 1; 1; 1; 1; 1;
                    1; 7; 7; 7; 7; 7; 7; 1;
                    1; 7;18;18;18;18; 7; 1;
                    1; 7;18;27;27;18; 7; 1;
                    1; 7;18;27;27;18; 7; 1;
                    1; 7;18;18;18;18; 7; 1;
                    1; 7; 7; 7; 7; 7; 7; 1;
                    1; 1; 1; 1; 1; 1; 1; 1
                    |]
                if Funcs2.Stage(psn)= End then
                    // Bonus for number of moves available
                    let ct = ref 0
                    let ctOK ord = 
                        let ordsqid = psn.Sqs.[ord]
                        if ordsqid = -1 || (psn.Pcs.[ordsqid].pcIsWhite <> pccol && psn.Pcs.[ordsqid].Name<>enmName.King) then 
                            ct:=!ct+1
                    let ords = Ref.movsK.[pcord]
                    List.iter ctOK ords
                    intPoints <- intPoints + !ct
                    // Bonus for being in centre of board
                    intPoints <- intPoints + kingAintSquareValues.[pcord]/10
                else// Opening & Middle
                    // Penalty for being in centre of board
                    intPoints <- intPoints - kingAintSquareValues.[pcord]/10
                intPoints
            |_-> raise InvalidName
    let PositionPoints(pr,psn) =
        let pp i = if psn.Pcs.[i].SquareOrd <> -1 then PositionalPoints(i,psn) else 0
        let ppl = if pr.prIsWhite then List.map pp [0..15] else List.map pp [16..31]
        List.fold (+) 0 ppl
    let PointsTotal(pcid,psn) =
        //System.Diagnostics.Debug.WriteLine("Piece " + psn.Pcs.[pcid].Name.abbrev + " colour " + pcid.ToString() + " Value " + Value(pcid,psn).ToString() + " PositionalPoints " + PositionalPoints(pcid,psn).ToString())
        Value(pcid,psn) + PositionalPoints(pcid,psn) //+ psn.Sqs.[pc.SquareOrd).Value;
    let Points(pr,psn) =
        let col = pr.prIsWhite
        let mutable intPoints = 0
        let mutable intBishopCount = 0
        let mutable intRookCount = 0
        let bot,top = if col then 0,15 else 16,31
        for i = bot to top do
            let piece = psn.Pcs.[i]
            if piece.SquareOrd <> -1 then 
                intPoints <- intPoints + PointsTotal(i,psn)
                if piece.Name = enmName.Bishop then intBishopCount <- intBishopCount+1
                if piece.Name = enmName.Rook then intRookCount <- intRookCount+1
        if (intBishopCount >= 2) then intPoints <- intPoints + 50
        if (intRookCount >= 2) then intPoints <- intPoints + 10
        if (psn.Mhst.Count > 0 && psn.Mhst.[psn.Mhst.Count - 1].IsThreeMoveRepetition) then
            intPoints <- intPoints + (if pr.Intellegence = Human then 1000000000 else 0)
        if (not col && psn.Bcas)||(col && psn.Wcas) then
            intPoints <- intPoints + 12
        else
            if (col && Funcs2.HasMoved(4,psn)||not col && Funcs2.HasMoved(20,psn)) then
                intPoints <- intPoints - 25
            else
                let pieceRookid = if col then psn.Sqs.[Ref.GetOrdFR (7,0)] else psn.Sqs.[Ref.GetOrdFR (7,7)]
                if (pieceRookid = -1 || psn.Pcs.[pieceRookid].Name <> enmName.Rook||psn.Pcs.[pieceRookid].pcIsWhite <> col||Funcs2.HasMoved(pieceRookid,psn)) then
                    intPoints <- intPoints - 11
                let pieceRookid = if col then psn.Sqs.[Ref.GetOrdFR (0,0)] else psn.Sqs.[Ref.GetOrdFR (0,7)]
                if (pieceRookid = -1 || psn.Pcs.[pieceRookid].Name <> enmName.Rook||psn.Pcs.[pieceRookid].pcIsWhite <> col||Funcs2.HasMoved(pieceRookid,psn)) then
                    intPoints <- intPoints - 11
        intPoints
    let Score(pr,psn,testCM,checks) =
        if psn.Scr <> -1 then
            if pr.prIsWhite = psn.PlayerToPlay.prIsWhite then psn.Scr else -psn.Scr
        else 
            let ans =
                if testCM && (Move_gen.in_mate_quick pr psn checks) then
                    if pr.prIsWhite=psn.PlayerToPlay.prIsWhite then -999999999 else 999999999
                else
                    Points(pr,psn) - Points(Attack.OtherPlayer(pr),psn)
            psn.Scr <- if pr.prIsWhite = psn.PlayerToPlay.prIsWhite then ans else -ans
            ans



